#!/usr/bin/env python3
"""
#  Copyright (C) 2008-2019 Michael Buesch <m@bues.ch>
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License version 3
#  as published by the Free Software Foundation.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

import getopt
import sys
try:
	from serial import *
except ImportError:
	print("ERROR: pyserial module not available.")
	print("On Debian Linux please do:  apt install python3-serial")
	sys.exit(1)

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


# Serial communication port configuration
CONFIG_BAUDRATE			= 115200
CONFIG_BYTESIZE			= 8
CONFIG_PARITY			= PARITY_NONE
CONFIG_STOPBITS			= 2

# The size of one message
MSG_SIZE			= 6
MSG_PAYLOAD_SIZE		= 4
# Message IDs
MSG_ID_MASK			= 0x3F
MSG_INVALID			= 0
MSG_ERROR			= 1
MSG_LOGMESSAGE			= 2
MSG_PING			= 3
MSG_PONG			= 4
MSG_GET_CURRENT_PRESSURE	= 5
MSG_CURRENT_PRESSURE		= 6
MSG_GET_DESIRED_PRESSURE	= 7
MSG_DESIRED_PRESSURE		= 8
MSG_SET_DESIRED_PRESSURE	= 9
MSG_GET_HYSTERESIS		= 10
MSG_HYSTERESIS			= 11
MSG_SET_HYSTERESIS		= 12
MSG_GET_CONFIG_FLAGS		= 13
MSG_CONFIG_FLAGS		= 14
MSG_SET_CONFIG_FLAGS		= 15
MSG_SET_VALVE			= 16
MSG_RESTARTED			= 17
MSG_SHUTDOWN			= 18
MSG_TURNON			= 19
MSG_GET_MAXIMA			= 20
MSG_MAXIMA			= 21
# Message flags
MSG_FLAG_QOVERFLOW		= 0x40
MSG_FLAG_REQ_ERRCODE		= 0x80
# Message error codes
MSG_ERR_NONE			= 0 # No error
MSG_ERR_CHKSUM			= 1 # Checksum error
MSG_ERR_NOCMD			= 2 # Unknown command
MSG_ERR_BUSY			= 3 # Busy
MSG_ERR_INVAL			= 4 # Invalid argument
MSG_ERR_NOREPLY			= -1 # internal. Not sent over wire.
# Config flags
CFG_FLAG_AUTOADJUST_ENABLE	= 0


def usage():
	print("Pressure control - remote configuration")
	print("")
	print("Usage: pctl-remote [OPTIONS] /dev/ttyS0")
	print("")
	print("-h|--help            Print this help text")
	print("-p|--noping          Don't initially ping the device")
	print("-f|--nofetch         Don't initially fetch the device state")
	print("-k|--noka            Don't send keep-alive pings")
	print("-l|--log FILE        Log status information to FILE")

def parseArgs():
	global opt_ttyfile
	global opt_noping
	global opt_nofetch
	global opt_noka
	global opt_logfile

	if len(sys.argv) < 2:
		usage()
		sys.exit(1)

	opt_ttyfile = sys.argv[-1]
	opt_noping = 0
	opt_nofetch = 0
	opt_noka = 0
	opt_logfile = None

	try:
		(opts, args) = getopt.getopt(sys.argv[1:-1],
			"hpfkl:",
			[ "help", "noping", "nofetch", "noka", "log=", ])
	except getopt.GetoptError:
		usage()
		sys.exit(1)

	for (o, v) in opts:
		if o in ("-h", "--help"):
			usage()
			sys.exit(0)
		if o in ("-p", "--noping"):
			opt_noping = 1
		if o in ("-f", "--nofetch"):
			opt_nofetch = 1
		if o in ("-k", "--noka"):
			opt_noka = 1
		if o in ("-l", "--log"):
			opt_logfile = v

class LogFile(QObject):
	def __init__(self, logfileName):
		QObject.__init__(self)
		self.fd = None
		if not logfileName:
			return
		try:
			self.fd = open(logfileName, "w+b")
		except IOError as e:
			print("Failed to open logfile %s: %s" % (logfileName, e.strerror))
			sys.exit(1)
		self.write("X/Y,X/Y lower threshold,X/Y upper threshold,"+\
			   "Z,Z lower threshold,Z upper threshold,\n")
		print("Logging to: %s" % logfileName)

	def write(self, message):
		if not self.fd:
			return
		if isinstance(message, str):
			message = message.encode("UTF-8", "ignore")
		self.fd.write(message)
		self.fd.flush()

	def logPressure(self, xy, xy_desired, xy_hyst, z, z_desired, z_hyst):
		xy_lower = xy_desired - xy_hyst
		xy_upper = xy_desired + xy_hyst
		z_lower = z_desired - z_hyst
		z_upper = z_desired + z_hyst
		self.write("%d,%d,%d,%d,%d,%d,\n" %\
			(xy, xy_lower, xy_upper, z, z_lower, z_upper))

class RemoteProtocol(QObject):
	def __init__(self, ttyfile):
		QObject.__init__(self)
		global remote

		try:
			remote = self
			self.serial = Serial(ttyfile, CONFIG_BAUDRATE,
					     CONFIG_BYTESIZE, CONFIG_PARITY,
					     CONFIG_STOPBITS)
			self.__setResetLine(False)
			QThread.msleep(100)
			self.serial.flushInput()

			self.devRestarted = False

			self.pollTimer = QTimer(self)
			self.pollTimer.timeout.connect(self.__poll)
			self.pollTimer.start(50)

			self.keepAliveTimer = QTimer(self)
			self.keepAliveTimer.timeout.connect(self.__keepAlive)
			if not opt_noka:
				self.keepAliveTimer.start(1000)

			if not opt_noping:
				reply = self.sendMessageSyncReply(MSG_PING, 0, b"", MSG_PONG)
				if reply:
					mainwnd.centralWidget().log.hostLog(
						"PING->PONG success. Device is alife.\n")
				else:
					mainwnd.centralWidget().log.hostLog(
						"Communication with device failed. "+\
						"No reply to PING request.\n")
		except (SerialException, OSError, IOError) as e:
			print(e)
			sys.exit(1)

	def __setResetLine(self, resetOn):
		# RTS is connected to the microcontroller reset line.
		# If RTS is logic high, the reset line is pulled low
		# and the microcontroller is put into reset.
		# If RTS is logic low, the microcontroller is in
		# normal operation.
		try:
			self.serial.setRTS(bool(resetOn))
		except (SerialException, OSError, IOError) as e:
			mainwnd.statusBar().showMessage("Failed to toggle reset. %s" % e)

	def rebootDevice(self):
		# Reboot the microcontroller
		self.__setResetLine(True)
		QThread.msleep(50)
		self.__setResetLine(False)
		QThread.msleep(50)

	def stopKeepAliveTimer(self):
		self.keepAliveTimer.stop()

	def __keepAlive(self):
		reply = self.sendMessageSyncReply(MSG_PING, 0, b"", MSG_PONG)
		if not reply:
			mainwnd.centralWidget().log.hostLog(self.tr(
				"Keep-alife: Device did not reply to ping "+\
				"request. Rebooting device...\n"))
			self.rebootDevice()
			if not mainwnd.centralWidget().fetchState():
				mainwnd.centralWidget().log.hostLog(self.tr(
					"Keep-alife: Failed to fetch configuration\n"))

	def __poll(self):
		try:
			if self.serial.inWaiting() >= MSG_SIZE:
				self.parseMessage(self.__readMessage())
		except (SerialException, OSError, IOError) as e:
			mainwnd.statusBar().showMessage("Failed to poll message. %s" % e)
			return
		if self.devRestarted:
			if mainwnd.centralWidget().fetchState():
				mainwnd.centralWidget().log.hostLog(self.tr("Device rebooted\n"))
				mainwnd.centralWidget().turnOnDevice()
				self.devRestarted = False

	def checksumMessage(self, msg):
		calc_crc = self.__crc8_update_buffer(0xFF, msg[0:-1])
		calc_crc ^= 0xFF
		want_crc = msg[-1]
		if calc_crc != want_crc:
			text = self.tr("ERROR: message CRC mismatch\n")
			mainwnd.centralWidget().log.hostLog(text)
			try:
				self.serial.flushInput()
			except (SerialException, OSError, IOError) as e:
				pass
			return False
		return True

	def __readMessage(self):
		msg = self.serial.read(MSG_SIZE)
		flags = msg[0] & ~MSG_ID_MASK
		if flags & MSG_FLAG_QOVERFLOW:
			mainwnd.centralWidget().log.hostLog(
				"Warning: TX queue overflow on the device")
		return msg

	def parseMessage(self, msg):
		if not self.checksumMessage(msg):
			return
		id = msg[0] & MSG_ID_MASK
		if id == MSG_LOGMESSAGE:
			str = self.getPayload(msg).rstrip(b'\0').decode("UTF-8", "ignore")
			mainwnd.centralWidget().log.devLog(str)
		if id == MSG_CURRENT_PRESSURE:
			mainwnd.centralWidget().parseCurrentPressureMsg(msg)
		if id == MSG_RESTARTED:
			self.devRestarted = True

	def getPayload(self, msg):
		return msg[1:-1]

	def sendMessage(self, id, flags, payload):
		"""Send a message"""
		assert(len(payload) <= MSG_PAYLOAD_SIZE)
		# Create the header
		msg = b"%c" % (id | flags)
		# Add the payload
		msg += payload
		# Pad the payload up to the constant size
		msg += b'\0' * (MSG_PAYLOAD_SIZE - len(payload))
		# Calculate the CRC
		crc = self.__crc8_update_buffer(0xFF, msg)
		crc ^= 0xFF
		# Add the CRC to the message
		msg += b"%c" % crc
		# Send the message
		assert(len(msg) == MSG_SIZE)
		try:
			self.serial.write(msg)
		except (SerialException, OSError, IOError) as e:
			mainwnd.statusBar().showMessage("Failed to send message. %s" % e)

	def sendMessageSyncReply(self, id, flags, payload, replyId):
		"""Send a message and synchronously wait for the reply."""
		self.pollTimer.stop()
		self.sendMessage(id, flags, payload)
		timeout = QDateTime.currentDateTime().addMSecs(500)
		try:
			while True:
				if QDateTime.currentDateTime() >= timeout:
					msg = None
					break
				if self.serial.inWaiting() < MSG_SIZE:
					QThread.msleep(1)
					continue
				msg = self.__readMessage()
				if not self.checksumMessage(msg):
					continue
				msgid = msg[0] & MSG_ID_MASK
				if msgid == replyId:
					break
				# This is not a reply to our message.
				self.parseMessage(msg)
		except (SerialException, OSError, IOError) as e:
			mainwnd.statusBar().showMessage("Failed to fetch reply. %s" % e)
			msg = b"\0" * MSG_SIZE
		self.pollTimer.start()
		return msg

	def sendMessageSyncError(self, id, flags, payload):
		"""Sends a message and synchronously waits for the MSG_ERROR reply."""
		flags |= MSG_FLAG_REQ_ERRCODE
		reply = self.sendMessageSyncReply(id, flags, payload, MSG_ERROR)
		if not reply:
			return MSG_ERR_NOREPLY
		return self.getPayload(reply)[0]

	def configFlagsFetch(self):
		reply = self.sendMessageSyncReply(MSG_GET_CONFIG_FLAGS, 0, b"",
						  MSG_CONFIG_FLAGS)
		if not reply:
			return None
		reply = remote.getPayload(reply)
		xy = reply[0]
		z = reply[1]
		return (xy, z)

	def configFlagsSet(self, island, flags):
		data = b"%c%c" % (island, flags)
		err = self.sendMessageSyncError(MSG_SET_CONFIG_FLAGS, 0, data)
		return err

	def setValve(self, islandId, valveNr, state):
		data = b"%c%c%c" % (islandId, valveNr, (state != 0))
		i = 5 # Retry a few times
		while i != 0:
			err = self.sendMessageSyncError(MSG_SET_VALVE, 0, data)
			if err == MSG_ERR_NONE:
				break
			i -= 1
		return err

	def __crc8_update_buffer(self, crc, buf):
		for c in buf:
			crc ^= c
			for i in range(8):
				if crc & 1:
					crc = (crc >> 1) ^ 0x8C
				else:
					crc >>= 1
		return crc & 0xFF

class StatusBar(QStatusBar):
	def showMessage(self, msg):
		print(msg)
		QStatusBar.showMessage(self, msg, 3000)

class LogBrowser(QTextEdit):
	def __init__(self, parent=None):
		QTextEdit.__init__(self, parent)

		self.msgs = []
		self.curDevMsg = ""

		self.setReadOnly(1)
		self.hostLog(self.tr("Pressure Control logging started\n"));

	def __commit(self):
		MSG_LIMIT = 100
		if len(self.msgs) > MSG_LIMIT:
			self.msgs = self.msgs[1:]
			assert(len(self.msgs) == MSG_LIMIT)
		self.setPlainText("".join(self.msgs))
		# Scroll to the end of the log
		vScroll = self.verticalScrollBar()
		vScroll.setValue(vScroll.maximum())

	def __dateString(self):
		date = QDateTime.currentDateTime()
		return str(date.toString("[hh:mm:ss] "))

	def devLog(self, text):
		text = str(text) # to python string
		if not text:
			return
		if not self.curDevMsg:
			text = self.__dateString() + "Dev: " + text
		self.curDevMsg += text
		if text[-1] in "\r\n":
			self.msgs.append(self.curDevMsg)
			self.curDevMsg = ""
			self.__commit()

	def hostLog(self, text):
		text = str(text) # to python string
		text = self.__dateString() + "Host: " + text
		self.msgs.append(text)
		self.__commit()

class PressureGauge(QWidget):
	def __init__(self, name, min, max, units, parent):
		QWidget.__init__(self, parent)
		self.min = min
		self.max = max
		self.units = units

		self.setLayout(QVBoxLayout())

		self.title = QLabel(name, self)
		self.title.setAlignment(Qt.AlignHCenter)
		self.layout().addWidget(self.title)

		self.dial = QDial(self)
		self.dial.setNotchesVisible(1)
		self.dial.setEnabled(0)
		self.dial.setSingleStep(100)
		self.dial.setPageStep(1000)
		self.dial.setNotchTarget(2)
		self.dial.setMinimum(int(min * 1000))
		self.dial.setMaximum(int(max * 1000))
		self.dial.setFixedSize(100, 100)
		self.layout().addWidget(self.dial)

		self.num = QLabel(self)
		self.num.setAlignment(Qt.AlignHCenter)
		self.layout().addWidget(self.num)

		self.layout().addStretch()
		self.setValue(0)

	def setValue(self, value):
		if (value < self.min):
			value = self.min
		if (value > self.max):
			value = self.max
		self.num.setText("%.2f %s" % (float(value), self.units))
		self.dial.setValue(int(value * 1000))

class ValveIslandWidget(QGroupBox):
	def __init__(self, name, islandId, parent):
		QGroupBox.__init__(self, name, parent)
		self.islandId = islandId

		self.setLayout(QGridLayout())

		self.gauge = PressureGauge("Current pressure", 0, 10, "Bar", self)
		self.layout().addWidget(self.gauge, 0, 0, 3, 1)

		h = QHBoxLayout()
		h.addStretch()
		self.autoCheckbox = QCheckBox(self.tr("Automatically adjust pressure"), self)
		self.autoCheckbox.stateChanged.connect(self.autoadjustChanged)
		h.addWidget(self.autoCheckbox)
		self.layout().addLayout(h, 0, 1)

		h = QHBoxLayout()
		h.addStretch()
		label = QLabel(self.tr("Desired pressure:"), self)
		h.addWidget(label)
		self.pressureSpin = QDoubleSpinBox(self)
		self.pressureSpin.setMinimum(0.5)
		self.pressureSpin.setMaximum(8)
		self.pressureSpin.setSingleStep(0.1)
		self.pressureSpin.setSuffix(self.tr(" Bar"))
		self.pressureSpin.valueChanged.connect(self.desiredPressureChanged)
		h.addWidget(self.pressureSpin)
		self.layout().addLayout(h, 1, 1)

		h = QHBoxLayout()
		h.addStretch()
		label = QLabel(self.tr("Hysteresis:"), self)
		h.addWidget(label)
		self.hystSpin = QDoubleSpinBox(self)
		self.hystSpin.setMinimum(0.05)
		self.hystSpin.setMaximum(8)
		self.hystSpin.setSingleStep(0.05)
		self.hystSpin.setSuffix(self.tr(" Bar"))
		self.hystSpin.valueChanged.connect(self.desiredHysteresisChanged)
		h.addWidget(self.hystSpin)
		self.layout().addLayout(h, 2, 1)

		h = QHBoxLayout()
		self.inButton = QPushButton(self.tr("IN-Valve"), self)
		self.inButton.pressed.connect(self.inValvePressed)
		self.inButton.released.connect(self.inValveReleased)
		h.addWidget(self.inButton)
		self.outButton = QPushButton(self.tr("OUT-Valve"), self)
		h.addWidget(self.outButton)
		self.outButton.pressed.connect(self.outValvePressed)
		self.outButton.released.connect(self.outValveReleased)
		self.layout().addLayout(h, 3, 0, 1, 2)

		self.autoadjustChanged(Qt.Unchecked)

	def desiredPressureChanged(self, value):
		if not self.parent().initialized:
			return
		mainwnd.centralWidget().pokeUiTimer()
		mbar = int(value * 1000)
		data = b"%c%c%c" % (self.islandId, (mbar & 0xFF), ((mbar >> 8) & 0xFF))
		err = remote.sendMessageSyncError(MSG_SET_DESIRED_PRESSURE, 0, data)
		if err != MSG_ERR_NONE:
			self.parent().log.hostLog(self.tr("Failed to change pressure. Error=%u\n" % err))
	
	def getDesiredPressure(self):
		return int(self.pressureSpin.value() * 1000)

	def desiredHysteresisChanged(self, value):
		if not self.parent().initialized:
			return
		mainwnd.centralWidget().pokeUiTimer()
		mbar = int(value * 1000)
		data = b"%c%c%c" % (self.islandId, (mbar & 0xFF), ((mbar >> 8) & 0xFF))
		err = remote.sendMessageSyncError(MSG_SET_HYSTERESIS, 0, data)
		if err != MSG_ERR_NONE:
			self.parent().log.hostLog(self.tr("Failed to change hysteresis. Error=%u\n" % err))

	def getHysteresis(self):
		return int(self.hystSpin.value() * 1000)

	def autoadjustChanged(self, state):
		self.inButton.setEnabled(state == Qt.Unchecked)
		self.outButton.setEnabled(state == Qt.Unchecked)
		if not self.parent().initialized:
			return
		mainwnd.centralWidget().pokeUiTimer()
		flags = remote.configFlagsFetch()
		if flags is None:
			self.parent().log.hostLog(self.tr("Failed to fetch config flags\n"))
			return
		flags = flags[self.islandId]
		if state == Qt.Checked:
			flags |= (1 << CFG_FLAG_AUTOADJUST_ENABLE)
		else:
			flags &= ~(1 << CFG_FLAG_AUTOADJUST_ENABLE)
		err = remote.configFlagsSet(self.islandId, flags)
		if err != MSG_ERR_NONE:
			self.parent().log.hostLog(self.tr("Failed to set config flags\n"))

	def inValvePressed(self):
		mainwnd.centralWidget().stopUiTimer()
		err = remote.setValve(self.islandId, 0, 1)
		if err != MSG_ERR_NONE:
			self.parent().log.hostLog(self.tr("Failed to switch valve 0 ON\n"))

	def inValveReleased(self):
		mainwnd.centralWidget().startUiTimer()
		err = remote.setValve(self.islandId, 0, 0)
		if err != MSG_ERR_NONE:
			self.parent().log.hostLog(self.tr("Failed to switch valve 0 OFF\n"))

	def outValvePressed(self):
		mainwnd.centralWidget().stopUiTimer()
		err = remote.setValve(self.islandId, 1, 1)
		if err != MSG_ERR_NONE:
			self.parent().log.hostLog(self.tr("Failed to switch valve 1 ON\n"))

	def outValveReleased(self):
		mainwnd.centralWidget().startUiTimer()
		err = remote.setValve(self.islandId, 1, 0)
		if err != MSG_ERR_NONE:
			self.parent().log.hostLog(self.tr("Failed to switch valve 1 OFF\n"))

class MainWidget(QWidget):
	def __init__(self, parent=None):
		QWidget.__init__(self, parent)
		self.initialized = False

		self.setLayout(QVBoxLayout())

		self.uiLock = QCheckBox(self.tr("User interface enabled"), self)
		self.uiLock.setCheckState(Qt.Unchecked)
		self.uiLock.stateChanged.connect(self.__uiLockChanged)
		self.layout().addWidget(self.uiLock)
		self.uiLockTimer = QTimer(self)
		self.uiLockTimer.timeout.connect(self.__uiLockTimerExpired)

		self.xy = ValveIslandWidget("X/Y joints", 0, self)
		self.layout().addWidget(self.xy)
		self.z = ValveIslandWidget("Z joint", 1, self)
		self.layout().addWidget(self.z)

		self.log = LogBrowser(self)
		self.layout().addWidget(self.log)

		self.__uiEnable(False)

	def __uiEnable(self, enabled):
		self.xy.setEnabled(enabled)
		self.z.setEnabled(enabled)

	def stopUiTimer(self):
		self.uiLockTimer.stop()

	def startUiTimer(self):
		self.uiLockTimer.start(10000)

	def pokeUiTimer(self):
		self.stopUiTimer()
		self.startUiTimer()

	def __uiLockChanged(self, state):
		enabled = (state != Qt.Unchecked)
		self.__uiEnable(enabled)
		if enabled:
			self.pokeUiTimer()
		else:
			self.stopUiTimer()

	def __uiLockTimerExpired(self):
		self.uiLock.setCheckState(Qt.Unchecked)

	def initializeState(self):
		if not opt_nofetch:
			if not self.fetchState():
				self.log.hostLog(
					"Failed to fetch active configuration "+\
					"from device.\n")
		self.turnOnDevice()
		self.initialized = True

	def turnOnDevice(self):
		error = remote.sendMessageSyncError(MSG_TURNON, 0, b"")
		if error:
			self.log.hostLog("Failed to turn on device\n")
		else:
			self.log.hostLog("Device turned on\n")

	def shutdown(self):
		remote.stopKeepAliveTimer()
		if self.initialized:
			error = remote.sendMessageSyncError(MSG_SHUTDOWN, 0, b"")
			if error != MSG_ERR_NONE:
				QMessageBox.critical(self,
					"Pressure Control",
					"Failed to shutdown device")

	def fetchState(self):
		# Get the current pressure
		reply = remote.sendMessageSyncReply(MSG_GET_CURRENT_PRESSURE, 0, b"",
						    MSG_CURRENT_PRESSURE)
		if not reply:
			print("Failed to fetch current pressure. No reply.")
			return False
		self.parseCurrentPressureMsg(reply)

		# Get the X/Y maxima
		reply = remote.sendMessageSyncReply(MSG_GET_MAXIMA, 0, b"%c" % 0,
						    MSG_MAXIMA)
		if not reply:
			print("Failed to fetch X/Y maxima. No reply.")
			return False
		reply = remote.getPayload(reply)
		pressureMbar = reply[0] | (reply[1] << 8)
		hysteresisMbar = reply[2] | (reply[3] << 8)
		self.xy.pressureSpin.setMaximum(float(pressureMbar) / 1000)
		self.xy.hystSpin.setMaximum(float(pressureMbar) / 1000)

		# Get the Z maxima
		reply = remote.sendMessageSyncReply(MSG_GET_MAXIMA, 0, b"%c" % 1,
						    MSG_MAXIMA)
		if not reply:
			print("Failed to fetch Z maxima. No reply.")
			return False
		reply = remote.getPayload(reply)
		pressureMbar = reply[0] | (reply[1] << 8)
		hysteresisMbar = reply[2] | (reply[3] << 8)
		self.z.pressureSpin.setMaximum(float(pressureMbar) / 1000)
		self.z.hystSpin.setMaximum(float(pressureMbar) / 1000)

		# Get the desired pressure
		reply = remote.sendMessageSyncReply(MSG_GET_DESIRED_PRESSURE, 0, b"",
						    MSG_DESIRED_PRESSURE)
		if not reply:
			print("Failed to fetch desired pressure. No reply.")
			return False
		reply = remote.getPayload(reply)
		xy_mbar = reply[0] | (reply[1] << 8)
		z_mbar = reply[2] | (reply[3] << 8)
		self.xy.pressureSpin.setValue(float(xy_mbar) / 1000)
		self.z.pressureSpin.setValue(float(z_mbar) / 1000)

		# Get the hysteresis
		reply = remote.sendMessageSyncReply(MSG_GET_HYSTERESIS, 0, b"",
						    MSG_HYSTERESIS)
		if not reply:
			print("Failed to fetch hysteresis. No reply.")
			return False
		reply = remote.getPayload(reply)
		xy_mbar = reply[0] | (reply[1] << 8)
		z_mbar = reply[2] | (reply[3] << 8)
		self.xy.hystSpin.setValue(float(xy_mbar) / 1000)
		self.z.hystSpin.setValue(float(z_mbar) / 1000)

		# Get the config flags
		flags = remote.configFlagsFetch()
		if flags is None:
			print("Failed to fetch config flags. No reply.")
			return False
		if flags[0] & (1 << CFG_FLAG_AUTOADJUST_ENABLE):
			self.xy.autoCheckbox.setCheckState(Qt.Checked)
		if flags[1] & (1 << CFG_FLAG_AUTOADJUST_ENABLE):
			self.z.autoCheckbox.setCheckState(Qt.Checked)

		return True

	def parseCurrentPressureMsg(self, msg):
		msg = remote.getPayload(msg)
		xy_mbar = msg[0] | (msg[1] << 8)
		z_mbar = msg[2] | (msg[3] << 8)
		self.xy.gauge.setValue(float(xy_mbar) / 1000)
		self.z.gauge.setValue(float(z_mbar) / 1000)
		logfile.logPressure(xy_mbar, self.xy.getDesiredPressure(),
				    self.xy.getHysteresis(),
				    z_mbar, self.z.getDesiredPressure(),
				    self.z.getHysteresis())

class MainWindow(QMainWindow):
	def __init__(self, parent=None):
		QMainWindow.__init__(self, parent)
		self.setWindowTitle(self.tr("Pneumatic pressure control"))

		mb = QMenuBar(self)
		ctlmen = QMenu(self.tr("Control"), mb)
		ctlmen.addAction(self.tr("Exit"), self.close)
		mb.addMenu(ctlmen)
		helpmen = QMenu(self.tr("Help"), mb)
		helpmen.addAction(self.tr("About"), self.about)
		mb.addMenu(helpmen)
		self.setMenuBar(mb)

		self.setStatusBar(StatusBar())
		self.setCentralWidget(MainWidget())

		self.resize(400, 350)

	def initializeState(self):
		self.centralWidget().initializeState()

	def shutdown(self):
		self.centralWidget().shutdown()

	def about(self):
		QMessageBox.information(self, self.tr("About"),
					self.tr("Pneumatic pressure control\n"
						"Copyright (c) 2008-2019 Michael Buesch"))

def main():
	global remote
	global mainwnd
	global app
	global logfile

	mainwnd = None

	app = QApplication(sys.argv)
	parseArgs()

	logfile = LogFile(opt_logfile)
	mainwnd = MainWindow()
	remote = RemoteProtocol(opt_ttyfile)

	mainwnd.initializeState()
	mainwnd.show()
	result = app.exec_()
	mainwnd.shutdown()
	exit(result)

if __name__ == "__main__":
	main()

